import collections
import threading
import socketio
#import cPickle as pickle
from datetime import datetime, timedelta

import httplib as http

class Pipe(object):
	def __init__(self):
		self.items = collections.deque()
		self.cond = threading.Condition()
		self.open = True
	def __iter__(self):
		return self
	def next(self):
		with self.cond:
			while self.open and not self.items:
				self.cond.wait()
			if not self.open:
				raise StopIteration
			else:
				return self.items.popleft()
	def add(self, item):
		with self.cond:
			if not self.open:
				raise RuntimeError('add called on a closed pipe')
			self.items.append(item)
			self.cond.notify()
	def close(self):
		with self.cond:
			self.open = False
			self.cond.notify()

class BaseRpcClient(object):

	def __getattr__(self, attr):
		class Accessor(object):
			__slots__ = ('_client', '_path')
			def __init__(self, client, base, attr):
				self._client = client
				self._path = '/'.join((base, attr))
			def __getattr__(self, attr):
				return Accessor(self._client, self._path, attr)
			def __call__(self, *args, **kwargs):
#				if not args and not kwargs:
#					obj = None
				if len(args) == 1 and not kwargs: # and args[0] is not None:
					obj = args[0]
				else:
					obj = http.VarArgs(args, kwargs)
				return self._client._exec_request('POST', self._path, obj)
			def get(self):
				return self._client._exec_request('GET', self._path, None)
			def set(self, value):
				return self._client._exec_request('PUT', self._path, value)
			def delete(self):
				return self._client._exec_request('DELETE', self._path, None)
		return Accessor(self, '', attr)

class SyncRpcClient(BaseRpcClient):

	def __init__(self, addr, contentmgr, timeout=None):
		self._contentmgr = contentmgr
		self._transport = http.ClientTransport(addr, timeout)
#		self._contmgr = http.ContentManager([http.TextMarshaller(), 
#											 http.XmlMarshaller(), 
#											 http.PickleMarshaller()])
	def close(self):
		self._transport.close()

	def _exec_request(self,  method, path, content):
		request = self._contentmgr.create_request(method, path, content)
		print "Sending:", request
		self._transport.send_request(request)
		response = self._transport.recv_response()
		print "Received:", response
		result = self._contentmgr.process_message(response)
		if response.success:
			return result
		else:
			raise http.HttpError(response.status, response.reason) # result if isinstance(result, Exception) else RpcError(response)

class AsyncRpcClient(BaseRpcClient):

	def __init__(self, addr, contentmgr, notify, timeout=None):
		self._contentmgr = contentmgr
		self._transport = http.ClientTransport(addr, timeout)
#		self._contmgr = http.ContentManager([http.TextMarshaller(), 
#											 http.XmlMarshaller(), 
#											 http.PickleMarshaller()])
		self._notify = notify
		self._pipe = Pipe()
		self._thrd = threading.Thread(target = self._clientproc)
		self._thrd.start()

	def close(self):
		self._pipe.close()
		self._thrd.join()
		self._transport.close()
		
	def _exec_request(self, method, path, content):
		request = self._contentmgr.create_request(method, path, content)
		self._pipe.add(request)
	
	def _clientproc(self):
		print "starting clientproc!"
		for request in self._pipe:
			print "sending:", request
			self._transport.send_request(request, True)
			response = self._transport.recv_response()
			print "recvd:", response
			result = self._contentmgr.process_message(response)
			self._notify(self, request.resource, result, response.success)
		print "stopping clientproc!"

			

class BaseRpcServer(object):

#	def __init__(self, proxy):
#		self._proxy = proxy
#		self._contmgr = http.ContentManager([http.TextMarshaller(), 
#											 http.XmlMarshaller(), 
#											 http.PickleMarshaller()])

	# This should never raise an exception, it should always return a valid response.
	def _exec_request(self, request):
		try:
			content = self._contentmgr.process_message(request)
			path = request.resource.split('?', 1)[0]
			if request.method == 'POST':
				result = self._call(path, content)
			elif request.method == 'GET':
				if content is None:
					result = self._getattr(path)
				else:
					result = self._call(path, content)
			elif request.method == 'PUT':
				result = self._setattr(path, content)
			elif request.method == 'DELETE':
				result = self._delattr(path)
			else:
				raise http.HttpError(501, 'Method not implemented')
#				status, reason, result = 501, 'Method not implemented', None
			response = self._contentmgr.create_response(result)
#			status, reason = 200, 'OK'
#			status = 200 if result is not None else 204
#			response = self._contentmgr.create_response(200, 'OK', result)
#		except http.HttpError as error:
#			response = error.create_response(self._contentmgr)
#			status, reason, result = error.status, error.reason, error.object
		except Exception as error:
			response = self._contentmgr.create_response(error=error)
#			status, reason, content = getattr(error, 'httpstatus', 500), str(error), traceback.format_exc()
#			response = self._contentmgr.create_response(status, reason, content)
#			response = self._contentmgr.create_response(500, 'Exception was raised', error)
#			status, reason, result = 500, 'Exception was raised', error
#		return self._contentmgr.create_response(status, reason, result)
		return response

	def _call(self, path, obj):
		fn = self._find_path(path, True)
		if not callable(fn):
			raise http.HttpError(405, 'Object not callable')
#		if obj is None:
#			args, kwargs = (), {}
		if type(obj) is http.VarArgs:
			args, kwargs = obj
		elif type(obj) is http.WebForm:
			args, kwargs = (), obj
		else:
			args, kwargs = (obj,), {}
		return fn(*args, **kwargs)

	def _getattr(self, path):
		obj, attr = self._find_path(path)
		try:
			return getattr(obj, attr)
		except AttributeError as error:
			raise http.HttpError(405, str(error))

	def _setattr(self, path, value):
		obj, attr = self._find_path(path)
		try:
			return setattr(obj, attr, value)
		except AttributeError as error:
			raise http.HttpError(405, str(error))

	def _delattr(self, path):
		obj, attr = self._find_path(path)
		try:
			return delattr(obj, attr)
		except AttributeError as error:
			raise http.HttpError(405, str(error))

	def _find_path(self, path, call=False):
		class Proxy(object):
 			def __init__(self, obj):
 				self.__dict__ = obj.__dict__
		path = path.split('/')
		if path[0]:
			raise http.HttpError(400, 'Invalid request path')
		obj = Proxy(self)
#		print 'top', dir(obj)
#		if path[1] not in self.__dict__:
#			raise http.HttpError(404, 'Path not found')
		try:
			for attr in path[1 : -1]:
				if attr.startswith('_'):
					raise http.HttpError(403, 'Request contains private attribute')
#				print attr, dir(obj)
				obj = getattr(obj, attr)
			attr = path[-1]
			if attr.startswith('_'):
				raise http.HttpError(403, 'Request contains private attribute')
			return (obj, attr) if not call else getattr(obj, attr)
		except AttributeError as error:
			raise http.HttpError(404, str(error))

class AsyncRpcThread(threading.Thread):

	def __init__(self, target, ios, args=()):
		name = 'RPC connection: {}'.format(ios.name)
		args = (self,) + args
		threading.Thread.__init__(self, target=target, name=name, args=args)
		self.ios = ios

	def close(self):
		self.ios.close()
		if self.ident:
			self.join()

class SyncRpcServer(BaseRpcServer):

	def __init__(self, addr, contentmgr):
#		BaseRpcServer.__init__(self, proxy)
		self._contentmgr = contentmgr
		self._listenios = socketio.open(addr, 'rwbs@')

	def close(self):
		self._listenios.close()

	def run(self, timeout=None):
		if timeout is not None:
			endtime = datetime.now() + timedelta(seconds=timeout)
		while timeout is None or timeout >= 0:
			self._listenios.timeout = timeout
			try:
				serverios = self._listenios.accept()
			except socketio.SocketError:
				break
#			if timeout is not None:
#				serverios.timeout = max((endtime - datetime.now()).total_seconds(), 0.0)
			with http.ServerTransport(serverios) as transport:
				try:
					request = transport.recv_request()
					print "Received:", request
				except http.HttpError as error:
#					status, reason, content = error.httpstatus, str(error), traceback.format_exc()
					response = self._contentmgr.create_response(error=error)
#					response = error.create_response(self._contentmgr)
				else:
#					if request:
					self.client_addr = serverios.name
					response = self._exec_request(request)
					self.client_addr = None
#					else:
#						response = transport.error_response
				print "Sending:", response
				transport.send_response(response)
			if timeout is not None:
				timeout = (endtime - datetime.now()).total_seconds()

class AsyncRpcServer(BaseRpcServer):

	def __init__(self, addr, contentmgr, notify, timeout=None):
#		BaseRpcServer.__init__(self, proxy)
		self._contentmgr = contentmgr
		self._notify = notify
		self._timeout = timeout
		self._listenios = socketio.open(addr, 'rwbs@')
		self._listenthrd = threading.Thread(target=self._listenproc)
		self._listenthrd.start()

	def close(self):
		self._listenios.close()
#		if self._listenthrd.ident:
		self._listenthrd.join()
		for thread in threading.enumerate():
			if type(thread) is AsyncRpcThread:
				thread.close()

	def status(self):
		return '\n'.join([thr.name for thr in threading.enumerate() if type(thr) is AsyncRpcThread])

	def _listenproc(self):
		for serverios in self._listenios.iter_accept():
			AsyncRpcThread(target=self._serverproc, ios=serverios).start()

	def _serverproc(self, thrd):
		# log connection starting
		print "connected from", thrd.ios.name
		thrd.ios.timeout = self._timeout
		thrd.event = threading.Event()
		def callback():
			self.client_addr = thrd.ios.name
			thrd.response = self._exec_request(thrd.request)
			self.client_addr = None
			thrd.event.set()
		with http.ServerTransport(thrd.ios) as transport:
			while transport.more():
				try:
					thrd.request = transport.recv_request(True)
					print "received:", thrd.request
				except http.HttpError as error:
#					status, reason, content = error.httpstatus, str(error), traceback.format_exc()
					thrd.response = self._contentmgr.create_response(error=error)
#					thrd.response = error.create_response(self._contentmgr)
				else:
#				if thrd.request:
					thrd.event.clear()
					self._notify(callback)
					thrd.event.wait()
#				else:
#					thrd.response = transport.error_response
				print "sending:", thrd.response
				transport.send_response(thrd.response)
		print "closing connection to", self.client_addr
		# log connection ending



if 1:
	mycontmgr = http.ContentManager([http.TextMarshaller(), 
									 http.XmlMarshaller(), 
									 http.VarargsMarshaller(http.PickleMarshaller, safe_imports=(datetime,))])

	def clinotify(cli, path, res, success):
		print "Client notification:", cli, path, res, success
	def makecli(addr):
		return AsyncRpcClient(addr, mycontmgr, clinotify)
	
	def srvnotify(cb):
		print "Server notification"
		cb()
	def doit(arg):
		print "doit!", arg
		return "this is what i received: {}".format(arg)
	def makesrv(addr):
		srv = AsyncRpcServer(addr, mycontmgr, srvnotify)
		srv.foo = 'foo'
		srv.bar = 42
		srv.doit = doit
		return srv

	def test_cli(addr, arg):
		cli = SyncRpcClient(addr, 20)
		print cli.zap(arg)
		
	def test_srv(addr, ret):
		class Foo(object):
			def zap(self, arg):
				print "zap!", arg
				return ret
		srv = SyncRpcServer(addr, Foo())
		try:
			srv.run(30)
		finally:
			srv.close()